<?php
/**
 * Default application init class.
 * The constructor of this class is run automatically before anything else is
 * done inside application. This class can be used to implement functionality
 * that need to be done even if authentication or other steps fail.
 * @author XXX XXX
 */
class Core_Application
{
    const TYPE_HTTP = 1;
    const TYPE_CONSOLE = 2;

    const DEFAULT_CONTROLLER = 'Main';
    const DEFAULT_ACTION = 'index';

    private $applicationPrefix = '';
    private $applicationType;

    public function __construct($applicationPrefix = '', $type = self::TYPE_HTTP)
    {
        $this->applicationPrefix = $applicationPrefix;
        $this->applicationType = $type;
    }
    /**
     *
     * @param <type> $applicationPrefix
     * @param <type> $type
     */
    public function execute()
    {
        try
        {
            list($controllerName, $action, $get) = $this->parseRequest();
            $requestObj = new Core_Request('/'.$this->applicationPrefix, $get, $_POST);
            $controllerObj = $this->initController($controllerName, $requestObj);
            $controllerObj->commitAction($action, $this->applicationType === self::TYPE_HTTP);
        }
        catch(Exception $e)
        {
            throw $e;
            $backupController = new Core_Controller(
                    new Core_Request('/'.$this->applicationPrefix)
            );
            $backupController->return404($e->getMessage());
        }
    }

    private function initController($controllerName, $requestObject)
    {
        if(!$this->controllerExists($controllerName))
            throw new Core_Application_Exception(
            "Call to undefined controller '$controllerName'");
        $controllerClass = "Controller_$controllerName";
        return new $controllerClass($requestObject);
    }
    /**
     * Chech if a controller exists or not
     * @param $controllerName
     * @return BOOL
     */
    private function controllerExists($controllerName)
    {
        $controllerName = ucfirst($controllerName);
        return file_exists(dirname(__FILE__)."/../Controller/$controllerName.php");
    }

    private function parseRequest()
    {
        if($this->applicationType === self::TYPE_HTTP)
            return $this->parseHttpRequest();
        else if($this->applicationType === self::TYPE_CONSOLE)
            return $this->parseConsoleRequest();
        else
            throw new Core_Application_Exception(
            'Unknown request type "'.$applicationType.'" encountered'
            );
    }

    /**
     * Parse HTTP request
     * Turn Server's "REQUEST_URI" string into list:
     *  0 => nameOfController
     *  1 => nameOfMethod
     *  2 => arrayOfGetParameters( ... )
     */
    private function parseHttpRequest()
    {
        //Ignore GET parameters from request
        list($request) = explode('?', $_SERVER['REQUEST_URI']);
        return $this->reformatRequest($this->requestToArray($request));
    }

    private function requestToArray($requestString)
    {
        // Get URL without leading and trailing "/" and possible $this->applicationPrefix
        // then explode by '/'
        $trimmed = rtrim( ltrim( $requestString, '/'.$this->applicationPrefix ) ,'/' );
        return empty ($trimmed)
                ? array()
                :explode('/', $trimmed);
    }

    /**
     * Reformat parsed request, fill default controller / action if needed
     *
     * Output array( 'nameOfController', 'nameOfAction', array('key' => 'value' ...))
     * @param $params Parsed request parameters
     * @return $reformatted Array of reformatted parameters
     */
    private function reformatRequest($params)
    {
        return array(
                isset($params[0]) && !empty($params[0]) ? $params[0] : self::DEFAULT_CONTROLLER,
                isset($params[1]) && !empty($params[1]) ? $params[1] : self::DEFAULT_ACTION,
                isset($params[2]) && !empty($params[2])
                    ? $this->parseGetParameters($params)
                    : array(),
                
        );

    }

    /**
     * Reformat get parameters from request into array
     * @param $params Array of request URL exploded with '/'
     * @return Array simulated GET-parameters
     */
    private function parseGetParameters($params)
    {
        $get = array();
        if(sizeof($params)<=2)
            return array();
        for($getParamNumber = 2 ; $getParamNumber < sizeof($params) ; $getParamNumber++)
        {
            list($key, $value) = $this->getParameterHasValue($params[$getParamNumber])
                    ? explode(':', $params[$getParamNumber])
                    : array($params[$getParamNumber], true);
            $get[$key] = $value;
        }
        return $get;
    }

    /**
     * Determinate does a key:value pair have value part
     * @param $getParameter A "key:value" pair or "key" as string
     * @return Boolean
     */
    private function getParameterHasValue($getParameter)
    {
        return preg_match('/^.+:.+$/', $getParameter) == 1;
    }

    /**
     * Parses console parameters from command line input.
     * Figures out controller, action, extension and parameters.
     * Simulates $_POST from input
     *  0 => nameOfController
     *  1 => nameOfMethod
     *  2 => arrayOfGetParameters( ... )
     * @global $argv Array holding system command line parameters
     */
    private function parseConsoleRequest()
    {
        global $argv;
        $output = array();
        if (isset($argv[1]))
        {
            $path = $argv[1];
            $output = $this->reformatRequest($this->requestToArray($path));
        }

        //This is to simuate POST-data from console
        if(isset($argv[2]))
        {
            $_POST = $this->simulatePostParameters($argv);
        }
        return $output;
    }

    /******************
     * Converts parameters from console to PHP associative array
     *
     * Function searches from flags indicated with hyphen "-" as the first
     * character (for example "-f"). Function stores flag (without hyphen)
     * as a key in array.
     * If only one value is found it is stored as the value of that key. If more
     * than one values are found, their stored into array and this array is set
     * as value of the key. If no values are found after flag, TRUE is stored
     * as the value of that key.
     *
     * Examples:
     *	parameters:				resulting array:
     *	-p -l foo				array('p' => TRUE, 'l' => 'foo')
     *  -adx foo				array('a' => TRUE, 'd' => TRUE, 'x' => 'foo')
     *	-v one two three			array('v' => array('one', 'two', 'three') )
     *	--help					array('help' => TRUE)
     *
     * @param $arguments	The parameters as in global $argv
     * @return $result		Array simulating $_POST
     ******************/
    private function simulatePostParameters(array $arguments)
    {
        $commands = array();

        foreach ($arguments as $i => $arg)
        {
            if(substr($arg, 0, 1) != '-') continue; //Skip values, only take the flags

            if(substr($arg, 0, 2) == '--')
            {
                $key = substr($arg, 2);
                if(!isset($commands[$key])) $commands[$key] = array(); //Set key

                //Go trough arguments again, this time skip all arguments before $i and break on first flag after $i
                foreach ($arguments as $j => $arg2)
                {
                    if($j <= $i) continue;
                    if(substr($arg2, 0, 1) == '-') break;
                    $commands[$key][] = $arg2;
                }
                //If only one value is found set it as the value for this key
                if(sizeof($commands[$key]) == 1) $commands[$key] = $commands[$key][0];
                //If no values were found set TRUE as the value for this key
                else if(sizeof($commands[$key]) == 0) $commands[$key] = true;
            }
            else if(substr($arg, 0, 1) == '-')
            {
                $keys = str_split(substr($arg, 1));
                foreach($keys as $k)
                {
                    if(!isset($commands[$k])) $commands[$k] = true;
                }

                $last = end($keys);

                if(is_bool($commands[$last])) $commands[$last] = array(); //Set key
                else if(is_string($commands[$last])) $commands[$last] = array($commands[$last]);

                //Go trough arguments again, this time skip all arguments before $i and break on first flag after $i
                foreach ($arguments as $j => $arg2)
                {
                    if($j <= $i) continue;
                    if(substr($arg2, 0, 1) == '-') break;
                    $commands[$last][] = $arg2;
                }

                //If only one value is found set it as the value for this key
                if(sizeof($commands[$last]) == 1) $commands[$last] = $commands[$last][0];
                //If no values were found set TRUE as the value for this key
                else if(sizeof($commands[$last]) == 0) $commands[$last] = true;
            }
        }

        return $commands;
    }
}
class Core_Application_Exception extends Exception
{

}